#+TITLE: Python IDE
#+AUTHOR: Zelphir Kaltstahl
#+STARTUP: content
#+STARTUP: indent
#+STARTUP: align
#+STARTUP: inlineimages
#+STARTUP: hideblocks
#+STARTUP: entitiesplain
#+STARTUP: nologdone
#+STARTUP: nologreschedule
#+STARTUP: nologredeadline
#+STARTUP: nologrefile
#+TODO: TODO INPROGRESS UNCLEAR | DONE CANCELLED
#+DATE: [2023-07-07 Fri]
#+LANGUAGE: English
#+PRIORITIES: A E E
#+KEYWORDS: emacs config python ide features lsp flake8 linter checker flycheck


* Installation

** LSP mode

First one needs to install the =lsp-mode= package for Emacs. If your Emacs packages are managed by GNU Guix, you can add =emacs-lsp-mode= to your manifest file (the =manifest.scm= file).

#+begin_src scheme
(specifications->manifest
 '(...
   "emacs-lsp-mode"
   ...
   ))
#+end_src

** Company

Also consider installing =emacs-company= to show completions:

#+begin_src scheme
(specifications->manifest
 '(...
   "emacs-company"
   ...
   ))
#+end_src

** Python language server

Emacs also needs a Python language server to run, with which it can communicate and from which it can get information about the code you write.

#+begin_src shell
. <venv-used-by-emacs>/bin/activate
python3 -m pip install python-language-server[all]
#+end_src

* Configuration

** UNCLEAR Flycheck

LSP mode will activate flycheck or flymake to help with checking your code.

#+begin_src elisp
(add-hook 'after-init-hook #'global-flycheck-mode)
(setq-default flycheck-temp-prefix ".flycheck")
#+end_src

** lsp-mode without use-package

*** Load the package

Make sure =lsp-mode= is installed and loaded:

#+begin_src elisp
(require 'lsp-mode)
#+end_src

*** LSP's own diagnostics

To disable the LSP diagnostics (and instead rely on =flake8=):

#+begin_src elisp
(setq lsp-diagnostics-provider :none)
#+end_src

*** Add lsp hook for Python

Start lsp when you open a file for each langauge. lsp-mode will start all the other minor modes necessary.

#+begin_src elisp
(add-hook 'python-mode-hook #'lsp)
#+end_src

*** Amount of documentation shown

Change the amount of documentation lsp-mode shows when your cursor rests on a function. Show all documentation.

#+begin_src elisp
(setq lsp-eldoc-render-all t)
#+end_src

*** Disable the pylint plugin of the language server

(We will use flake8, not pylint.)

#+begin_src elisp
(setq lsp-pylsp-plugins-pylint-enabled nil)
#+end_src

*** UNCLEAR Set =flake8= as a checker

This step is still unclear, because it might not be needed, since the checker config is also done in the =dir-locals.el= file.

#+begin_src elisp
(setq flycheck-checker 'python-flake8)
#+end_src

** Project site configuration

*** Set Emacs handling of local variables

See: [[https://www.gnu.org/software/emacs/manual/html_node/elisp/File-Local-Variables.html#index-enable_002dlocal_002dvariables]]:

#+begin_src elisp
(setq enable-local-variables t)
#+end_src

This will make Emacs:

#+begin_quote
Set the safe variables, and query (once) about any unsafe variables.
#+end_quote

*** UNCLEAR =dir-locals.el= in root directory of project

(Still unclear if all settings are needed.)

Add a =dir-locals.el= in the root directory of your project and don't forget to add it to =.gitignore=:

#+begin_src elisp
((python-mode . ((fill-column . 80)
                 (flycheck--automatically-disabled-checkers
                  . '(python-pylint lsp python-pycompile))
                 (flycheck--automatically-enabled-checkers
                  . '(python-flake8))
                 (lsp-clients-pylsp-library-directories
                  . ("<venv-directory>/lib/python3.10/site-packages"))
                 (lsp-pylsp-plugins-jedi-completion-enabled . nil)

                 ;; Somehow lsp-pylsp-plugins-flake8-config is ignored
                 ;; or does not work.
                 ;; (lsp-pylsp-plugins-flake8-config . "<project-directory>/.flake8"))
                 ;; Using the general flycheck setting instead.
                 (flycheck-flake8rc . "/home/hans/dev/stackfuel/.flake8")

                 (lsp-pylsp-plugins-flake8-enabled . t)
                 (lsp-pylsp-plugins-flake8-max-line-length . 80)
                 ;; The Python executable to use for running flake8.
                 (flycheck-python-flake8-executable
                  . "<venv-directory>/bin/python3"))))
#+end_src

There might be an alternative to hardcoding the venv directory:

#+begin_src elisp
((python-mode .
              ((eval . (let ((filename
                              (file-name-concat
                               ;; ensures there is a slash at the end
                               (file-name-as-directory
                                (let ((d (dir-locals-find-file ".")))
                                  (if (stringp d) d (car d))))
                               "<venv-name>/bin/python3")))
                         (cons 'flycheck-python-flake8-executable filename))))))
#+end_src

But I could not get that to work yet.

There is also apparently some flag that needs to be set to allow evaluation of expressions in a =dir-locals.el= file. This also indicates, that this method might not be the safest.

** Notes

+ company will provide auto complete suggestions
+ flycheck will highlight warnings and errors
+ xref can find the definition of a function or variable
+ eldoc will show function documentation in the minibuffer

* Safe local variables

When opening a Python file which resides in a directory or subdirectory of a directory in which a =.dir-locals.el= file exists, Emacs will ask you about applying the settings within the =.dir-locals.el= file to your buffer. This can be avoided by marking the settings as safe:

#+begin_src elisp
(put 'lsp-pylsp-plugins-jedi-completion-enabled 'safe-local-variable (lambda (_) t))
(put 'lsp-pylsp-plugins-flake8-max-line-length 'safe-local-variable (lambda (_) t))
(put 'lsp-pylsp-plugins-flake8-enabled 'safe-local-variable (lambda (_) t))
(put 'lsp-pylsp-plugins-flake8-config 'safe-local-variable #'stringp)
(put 'flycheck-add-next-checker 'safe-local-variable (lambda (_) t))
(put 'flycheck--automatically-enabled-checkers 'safe-local-variable (lambda (_) t))
(put 'flycheck--automatically-disabled-checkers 'safe-local-variable (lambda (_) t))
(put 'flycheck-checker 'safe-local-variable (lambda (_) t))
(put 'lsp-pylsp-plugins-flake8-ignore 'safe-local-variable (lambda (_) t))
#+end_src

Possibly using something like:

#+begin_src elisp
(add-to-list 'safe-local-variable-values '(lsp-pylsp-plugins-jedi-completion-enabled . nil))
#+end_src

is a better way to set which variables are safe.

* Usage

** Check configuration

The keyboard shortcut is: =C-c ! v=.

The procedure name is: flycheck-verify-setup.
